#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "imageADT.h"
#include "bmpfile.h"
#include "util.h"
#include <openssl/evp.h>

#define FERROR(label, errorCode) 	{\
										fprintf(stderr, label);\
										return errorCode;\
									}

#define FALSE 0
#define TRUE 1
#define KEY_SIZE 16

/*Definicion de tamanos de bloque para las dos primitivas*/
#define AES_BLOCK_SIZE 128
#define DES_BLOCK_SIZE 64

/* Estructra imageCDT
 * ==================
 * Estructura concreta para guardar los datos de una imagen
 */
typedef struct imageCDT{
	bmpInfoType bmpInfo;
	char *data;
} imageCDT;


/*
*  DEFINICION DE FUNCIONES ESTATICAS INTERNAS AL MODULO
*  ====================================================
*/
static int decrypt( unsigned char **decryptedText, unsigned char *encryptedText,
					int encryptedTextSize, unsigned char *mykey, unsigned char*iv,
					const char *EVPmodeString );

static int encrypt( unsigned char **encryptedText, unsigned char *plainText,
					int plainTextSize, unsigned char *mykey, unsigned char *iv,
					const char *EVPmodeString );


/*
*  IMPLEMENTACION DE LAS FUNCIONES PUBLICAS
*  ========================================
*/

int
newImage( imageADT *image)
{
	if( image == NULL )
		return 1;
	if ( (*image = malloc(sizeof(imageCDT))) == NULL )
		return -1;
	(*image)->bmpInfo = NULL;
	(*image)->data = NULL;	
	
	return 0;
}



int 
freeImage( imageADT *image)
{
	if( image == NULL )
		return 1;
	if( *image != NULL )
	{
		if( (*image)->bmpInfo != NULL )
			free( (*image)->bmpInfo );		
		if( (*image)->data != NULL )
			free( (*image)->data);
		free(*image);
		*image = NULL;
	}
	return 0;
}



int 
loadImage( imageADT *image, FILE *input, int blockSize)
{
	bmpInfoType informacion;
	char *matrix;
	int imageSize, errorCode;

	if ( input == NULL || image == NULL || *image == NULL )
		return 1;

	/* Se procesa el header del archivo bmp para obtener la informacion de
	 * la imagen. La misma se almacena en una estructura de tipo bmpInfo */	
	if ( (errorCode=readBMPInfo (input, &informacion)) != 0 )
	{
		printf("loadImage(): Error readBMPInfo, Code = %d", errorCode);
		return 2;
	}

	/* Nos fijamos si la imagen esta comprimida */
	if( getImageCompression(informacion) != 0 )
	{
		printf("\nloadImage(): Error: La imagen no debe estar comprimida. Intente con otra imagen.\n");
		return 3;
	}

	/* Nos fijamos si la imagen es multiplo de bloque */
	imageSize = getImageSize(informacion);
	if( imageSize % blockSize != 0 )
	{
		printf("\nloadImage(): Error: La cantidad de bytes de la imagen no es ");
		printf("multiplo del tamaño de bloque. Intente con otra imagen.\n");
		return 3;	
	}

	(*image)->bmpInfo = informacion;
	if( ( matrix = malloc( imageSize )) == NULL )
		return 5;
	
	/* Se carga la imagen del archivo a una matriz. */

	if ( ( errorCode = readBMPData2 (matrix, imageSize, input)) < 0 )
	{
		printf("LoadImage(): error al usar readBMPData2. Code %d\n", errorCode);
		free(matrix);
		return 6;
	}
	(*image)->data = matrix;

	return 0;
}

int 
saveImage( imageADT *image, FILE *output)
{	
	int height, width;

	/* Se controlan los parametros de entrada. */
	if ( output == NULL || image == NULL || *image == NULL || 
	(*image)->data == NULL || (*image)->bmpInfo == NULL )
		return 1;
	
	height = getHeight((*image)->bmpInfo);
	width = getWidth((*image)->bmpInfo);
	
	int imageSize = getImageSize((*image)->bmpInfo);
	/* Se guarda el header de la imagen en el archivo, con la informacion
	 * correspondiente. 
	 */
	if ( writeBMPInfo ( output, (*image)->bmpInfo ) )
		return 2;
	
	/* Se guarda el mapa de bits que contiene la matriz en el archivo. */
	int errorCode;
	if ( (errorCode=writeBMPData2 ( (*image)->data, imageSize, output)) < 0 )
	{
		printf("SaveImage(): error al usar writeBMPData2. Code %d\n", errorCode);
		return 3;
	}

	return 0;
}


int
encryptImage( imageADT *image, const char *modeString, unsigned char *mykey, unsigned char *iv )
{
	unsigned char *encryptedData;


	if( (*image)->bmpInfo == NULL || (*image)->data == NULL )
		return 1;
	if( modeString == NULL || mykey == NULL || iv == NULL )
		return 2;
	int plainDataSize = getImageSize( (*image)->bmpInfo );

	if( encrypt( &encryptedData, (unsigned char *)(*image)->data, plainDataSize, mykey, iv, modeString ) != 0 )
		return 3;
	free( (*image)->data );
	(*image)->data = (char *)encryptedData;

	return 0;
}

int
decryptImage( imageADT *image, const char *modeString, unsigned char *mykey, unsigned char *iv )
{
	int errorCode;
	unsigned char *decryptedData;
	if( (*image)->bmpInfo == NULL || (*image)->data == NULL )
		return 1;
	if( modeString == NULL || mykey == NULL || iv == NULL )
		return 2;
	int cryptedDataSize = getImageSize( (*image)->bmpInfo );

	if( (errorCode=decrypt( &decryptedData, (unsigned char *)(*image)->data, cryptedDataSize, mykey, iv, modeString )) != 0 )
	{
		printf("decryptImage(): decrypt tiro error code %d\n", errorCode);
		return 3;
	}

	free( (*image)->data );

	(*image)->data = (char *)decryptedData;

	return 0;
}

static int
encrypt( unsigned char **encryptedText, unsigned char *plainText, int plainTextSize, unsigned char *mykey, unsigned char *iv, const char *EVPmodeString )
{
	int encryptedTextSize, paddingResult;
	int termTextSize = 0;
	EVP_CIPHER_CTX ctx;

	if( ( *encryptedText = malloc( plainTextSize) ) == NULL )
		return 1;

	EVP_EncryptInit(&ctx, EVP_get_cipherbyname( EVPmodeString ), mykey, iv);

    EVP_CIPHER_CTX_set_padding(&ctx, 0);

    EVP_EncryptUpdate(&ctx, *encryptedText, &encryptedTextSize, plainText, plainTextSize);
    paddingResult = EVP_EncryptFinal(&ctx, &(*encryptedText)[encryptedTextSize], &termTextSize);

    EVP_CIPHER_CTX_cleanup(&ctx);
    if( paddingResult == 0 )
    	return 2;

    return 0;
}

static int
decrypt( unsigned char **decryptedText, unsigned char *encryptedText, int encryptedTextSize, unsigned char *mykey, unsigned char*iv, const char *EVPmodeString )
{
	int decryptedTextSize, paddingResult ;
	EVP_CIPHER_CTX ctx;
	int termTextSize = 0;

	if( ( *decryptedText = malloc( encryptedTextSize) ) == NULL )
		return 1;
	EVP_DecryptInit(&ctx, EVP_get_cipherbyname( EVPmodeString ), mykey, iv);

	EVP_CIPHER_CTX_set_padding(&ctx, 0);
	EVP_DecryptUpdate(&ctx, *decryptedText, &decryptedTextSize, encryptedText, encryptedTextSize);

	paddingResult = EVP_DecryptFinal(&ctx, &(*decryptedText)[decryptedTextSize], &termTextSize);

	EVP_CIPHER_CTX_cleanup(&ctx);

	if( paddingResult == 0 )
		return 2;
	return 0;
}
